Librerias

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(quantmod)
## Warning: package 'quantmod' was built under R version 4.4.3
## Cargando paquete requerido: xts
## Cargando paquete requerido: zoo
## 
## Adjuntando el paquete: 'zoo'
## 
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## 
## 
## ######################### Warning from 'xts' package ##########################
## #                                                                             #
## # The dplyr lag() function breaks how base R's lag() function is supposed to  #
## # work, which breaks lag(my_xts). Calls to lag(my_xts) that you type or       #
## # source() into this session won't work correctly.                            #
## #                                                                             #
## # Use stats::lag() to make sure you're not using dplyr::lag(), or you can add #
## # conflictRules('dplyr', exclude = 'lag') to your .Rprofile to stop           #
## # dplyr from breaking base R's lag() function.                                #
## #                                                                             #
## # Code in packages is not affected. It's protected by R's namespace mechanism #
## # Set `options(xts.warn_dplyr_breaks_lag = FALSE)` to suppress this warning.  #
## #                                                                             #
## ###############################################################################
## 
## Adjuntando el paquete: 'xts'
## 
## The following objects are masked from 'package:dplyr':
## 
##     first, last
## 
## Cargando paquete requerido: TTR
## Warning: package 'TTR' was built under R version 4.4.3
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo

Referencias sobre Analisis Tecnico

Basico: https://corporatefinanceinstitute.com/resources/career-map/sell-side/capital-markets/technical-analysis/

Velas: https://bookdown.org/kochiuyu/technical-analysis-with-r-second-edition2/candle-stick-pattern.html

Dojis: https://bookdown.org/kochiuyu/technical-analysis-with-r-second-edition2/doji.html

Estrategias: https://trendspider.com/learning-center/technical-analysis-strategies/

Estrategias de Cruces de Promedios Moviles: implementar gloden cross

Estrategia MACD: implementar regla sobre cero (https://www.youtube.com/watch?v=W78Xg_pnJ1A)

Estrategia RSI: implementar regla 30-70

Avanzado (basado en el paquete ““, no lo usamos): https://rpubs.com/jwcb1025/quantstrat_trading_strategy

Analisis Tecnico con R: https://bookdown.org/kochiuyu/technical-analysis-with-r-second-edition2/

Usando xts: https://rpubs.com/odenipinedo/manipulating-time-series-data-with-xts-and-zoo-in-R

Datos

Fuente: https://www.kaggle.com/datasets/jakewright/9000-tickers-of-stock-market-data-full-history?resource=download

# datos<-read_csv("/home/andresfaral/Downloads/all_stock_data.csv")
# dim(datos)
# tabla<-table(datos$Ticker)
# maximo<-max(tabla)
# tickers<-names(tabla[tabla==maximo])
# datos.filt<-datos %>% filter(Ticker %in% tickers)
# datos.filt
# save(datos.filt,file="stock_comp")
# Cargo datos de Empresas con la historia COMPLETA
load(file="stock_comp")
datos.filt

EDA

Summary

summary(datos.filt)
##       Date               Ticker               Open               High         
##  Min.   :1962-01-02   Length:458751      Min.   :  0.0000   Min.   :  0.0019  
##  1st Qu.:1977-10-14   Class :character   1st Qu.:  0.7825   1st Qu.:  1.0854  
##  Median :1993-06-08   Mode  :character   Median :  6.9453   Median :  7.2564  
##  Mean   :1993-06-11                      Mean   : 24.1942   Mean   : 24.5616  
##  3rd Qu.:2009-02-19                      3rd Qu.: 27.8408   3rd Qu.: 28.1586  
##  Max.   :2024-11-04                      Max.   :479.0400   Max.   :480.2800  
##       Low               Close              Volume            Dividends      
##  Min.   :  0.0019   Min.   :  0.0019   Min.   :        0   Min.   : 0.0000  
##  1st Qu.:  1.0637   1st Qu.:  1.0746   1st Qu.:   521442   1st Qu.: 0.0000  
##  Median :  7.0900   Median :  7.1800   Median :  1983700   Median : 0.0000  
##  Mean   : 24.0654   Mean   : 24.3183   Mean   :  4113892   Mean   : 0.0036  
##  3rd Qu.: 27.5219   3rd Qu.: 27.8440   3rd Qu.:  5301103   3rd Qu.: 0.0000  
##  Max.   :476.3700   Max.   :477.6200   Max.   :442012304   Max.   :51.0600  
##   Stock Splits     
##  Min.   :0.000000  
##  1st Qu.:0.000000  
##  Median :0.000000  
##  Mean   :0.000686  
##  3rd Qu.:0.000000  
##  Max.   :4.000000

Tabla de tickers

table(datos.filt$Ticker)
## 
##    AA   AEP    BA    BP   CAT   CNP   CVX   DIS   DTE    ED    FL    GD    GE 
## 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 
##    GT   HON   HPQ   IBM    IP   JNJ    KO    KR   MMM    MO   MRK   MRO   MSI 
## 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 15819 
##    PG   XOM   XRX 
## 15819 15819 15819

Usando quantmod

Fuente: https://www.quantmod.com/

Convierto una serie a xts para manejarlo con quantmod

# convierto 1 accion a xts
datos.filt2<-datos.filt %>% filter(Ticker=="KO") # just coke
KO.xts<- xts(datos.filt2[,c(3:7)],order.by = datos.filt2$Date)
#
class(KO.xts)
## [1] "xts" "zoo"
head(KO.xts)
##                   Open        High         Low       Close  Volume
## 1962-01-02 0.004007111 0.004116209 0.004007111 0.004007111  806400
## 1962-01-03 0.003947602 0.003947602 0.003858325 0.003917833 1574400
## 1962-01-04 0.003927766 0.003977356 0.003927766 0.003947602  844800
## 1962-01-05 0.003947603 0.003997192 0.003848408 0.003858326 1420800
## 1962-01-08 0.003828574 0.003828574 0.003744263 0.003818656 2035200
## 1962-01-09 0.003818655 0.003907917 0.003788901 0.003888081  960000

Visualizacion usando dygraphs

require(dygraphs)
## Cargando paquete requerido: dygraphs
dygraph(KO.xts$Close, main = "Coca Cola") %>%
  dyAxis("y", label = "Precio") %>%
  dyOptions(stackedGraph = FALSE)

Visualizando Precio al cierre y Volumen

dygraph(KO.xts) %>%
  dySeries("Close", label = "Close", color = "#0198f9", drawPoints = TRUE, pointSize = 3, pointShape = "square") %>%
  dySeries("Volume", label = "Trade Volume (M)", stepPlot = TRUE, fillGraph = TRUE, color = "#FF9900", axis = "y2")

Traigo la accion de google de yahoo finance

getSymbols("GOOG",src="yahoo") # from google finance
## [1] "GOOG"
class(GOOG)
## [1] "xts" "zoo"
tail(GOOG)
##            GOOG.Open GOOG.High GOOG.Low GOOG.Close GOOG.Volume GOOG.Adjusted
## 2025-04-11   155.585    159.86  155.585     159.40    22582000        159.40
## 2025-04-14   162.310    164.03  159.920     161.47    18255900        161.47
## 2025-04-15   161.570    162.05  157.645     158.68    15690800        158.68
## 2025-04-16   155.470    158.18  153.910     155.50    16921500        155.50
## 2025-04-17   156.610    157.07  150.900     153.36    19513400        153.36
## 2025-04-21   150.965    151.06  148.400     149.86    16120200        149.86

Charts financieros

Las series de OHLC

chartSeries(KO.xts) # toda la serie

chartSeries(KO.xts,subset="2000") # un anio especifico

chartSeries(KO.xts,subset="2000-01") # un anio y mes especifico

chartSeries(KO.xts,subset="2000-01::2000-03") # un periodo

chartSeries(KO.xts,subset="last 12 months") # ultimo anio

Agregado de Indicadores

Mirar:

Moving Averages: https://en.wikipedia.org/wiki/Moving_average

EMA: https://en.wikipedia.org/wiki/Exponential_smoothing

EMA en TTR: https://bookdown.org/kochiuyu/technical-analysis-with-r-second-edition2/exponential-moving-average-ema.html#ttr-1

# por separado
chartSeries(GOOG,subset="2010")

addEMA(200)

addEMA(50)

addEMA(7)

# todo junto
chartSeries(GOOG,subset="2010",TA="addEMA(200);addEMA(50);addEMA(7)")

Calculando Indicadores

ema7<-EMA(GOOG$GOOG.Open,7)
class(ema7)
## [1] "xts" "zoo"
head(ema7,10)
##                 EMA
## 2007-01-03       NA
## 2007-01-04       NA
## 2007-01-05       NA
## 2007-01-08       NA
## 2007-01-09       NA
## 2007-01-10       NA
## 2007-01-11 11.99885
## 2007-01-12 12.12486
## 2007-01-16 12.25398
## 2007-01-17 12.32493

Mas visualizaciones

candleChart(GOOG[1:20],multi.col=TRUE,theme="white")

candleChart(GOOG[10:14],multi.col=FALSE,theme="white")

chartSeries(GOOG[1:300,])

addMACD()

addBBands()

barChart(KO.xts)

barChart(KO.xts[1:30])

chartSeries(to.weekly(KO.xts),up.col='white',dn.col='blue')

chartSeries(to.monthly(KO.xts),up.col='white',dn.col='blue')

chartSeries(to.yearly(KO.xts),up.col='white',dn.col='blue')

chartSeries(KO.xts,TA="addRSI()")

Estrategias de Trading

Se compra 1 accion con la señial bulish y se vende 1 accion con la selian bearish Se empiezo siempre con una compra Se comienza con saldo 0, una compra disminuye/aumenta el saldo en el precio de compra/venta No puede haber mas ventas que compras (no se puede shortear) Al final del periodo se vende todo el stock (si hubiera) El resultado (target) es el saldo al final del

Deteccion de dojis

datos.filt2.doji <- datos.filt2 %>%
  mutate(
    dragonfly = ifelse(
      (abs(Close - Open) / ((Open + Close) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - High) / ((((Open + Close) / 2) + High) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - Low) / ((((Open + Close) / 2) + Low) / 2) > 0.01),
      1, 0
    ),
    gravestone = ifelse(
      (abs(Close - Open) / ((Open + Close) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - Low) / ((((Open + Close) / 2) + Low) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - High) / ((((Open + Close) / 2) + High) / 2) > 0.01),
      1, 0
    )
  )

sum(datos.filt2.doji$dragonfly)
## [1] 618
sum(datos.filt2.doji$gravestone)
## [1] 523

Calculo de proporciones poblacionales

datos.filt2.doji <- datos.filt2.doji %>%
  mutate(increase_nd = ifelse(is.na(lead(Close)), FALSE, Close < lead(Close)))

p_pob_increase <- sum(datos.filt2.doji$increase_nd)/nrow(datos.filt2.doji)

datos.filt2.doji.dragon <- datos.filt2.doji %>%
  filter(dragonfly == 1)

p_dragon_increase <- sum(datos.filt2.doji.dragon$increase_nd)/nrow(datos.filt2.doji.dragon)

datos.filt2.doji.grave <- datos.filt2.doji %>%
  filter(gravestone == 1)

p_grave_decrease <- 1 - sum(datos.filt2.doji.grave$increase_nd)/nrow(datos.filt2.doji.grave)

p_pob_decrease <- 1 - p_pob_increase

p_pob_increase
## [1] 0.4928251
p_dragon_increase
## [1] 0.4110032
p_pob_decrease
## [1] 0.5071749
p_grave_decrease
## [1] 0.499044

Calculo de la diferencia porcentual

datos.filt2.doji <- datos.filt2.doji %>%
  mutate(porc_diff_nd = ifelse(is.na(lead(Close)), 0, (lead(Close) - Close)/Close))

p_mean_diff <- mean(datos.filt2.doji$porc_diff_nd)

datos.filt2.doji.dragon <- datos.filt2.doji %>%
  filter(dragonfly == 1)

mean_dragon_diff <- mean(datos.filt2.doji.dragon$porc_diff_nd)

datos.filt2.doji.grave <- datos.filt2.doji %>%
  filter(gravestone == 1)

mean_grave_diff <- mean(datos.filt2.doji.grave$porc_diff_nd)

Aplicandolo a dias posteriores

library(dplyr)

calcular_dif_porcentual <- function(datos) {
  datos <- datos %>%
    mutate(
      diff_1 = (lead(Close, 1) - Close) / Close,
      diff_2 = (lead(Close, 2) - Close) / Close,
      diff_3 = (lead(Close, 3) - Close) / Close,
      diff_4 = (lead(Close, 4) - Close) / Close,
      diff_5 = (lead(Close, 5) - Close) / Close,
      diff_6 = (lead(Close, 6) - Close) / Close,
      diff_7 = (lead(Close, 7) - Close) / Close
    )

  # Función auxiliar para calcular los promedios de diferencias
  calcular_promedios <- function(df) {
    colMeans(df %>% select(starts_with("diff_")), na.rm = TRUE)
  }

  # Calcular promedios para la población total, con Gravestone y con Dragonfly
  total_mean <- calcular_promedios(datos)
  grave_mean <- calcular_promedios(datos %>% filter(gravestone == 1))
  dragon_mean <- calcular_promedios(datos %>% filter(dragonfly == 1))

  # Crear la tabla final
  tabla_resultado <- rbind(
    "Total" = total_mean,
    "Gravestone Doji" = grave_mean,
    "Dragonfly Doji" = dragon_mean
  )

  return(as.data.frame(tabla_resultado))
}

# Uso de la función
tabla_doji <- calcular_dif_porcentual(datos.filt2.doji)
print(tabla_doji)
##                        diff_1        diff_2      diff_3      diff_4      diff_5
## Total            0.0007245497  0.0014559828 0.002179687 0.002903392 0.003622175
## Gravestone Doji  0.0019373561  0.0028653237 0.002862548 0.004107481 0.004407989
## Dragonfly Doji  -0.0012537717 -0.0001856587 0.001220299 0.001642666 0.001952062
##                      diff_6      diff_7
## Total           0.004342560 0.005060004
## Gravestone Doji 0.005512973 0.007154211
## Dragonfly Doji  0.003270228 0.003611660

Distribucion de las diferencias porcentuales entre dojis

datos.filt2.doji.gd <- datos.filt2.doji %>%
  filter(dragonfly == 1 | gravestone == 1)

ggplot(datos.filt2.doji.gd, aes(x = factor(dragonfly)
                                , y = porc_diff_nd, fill = dragonfly)) +
  geom_violin(fill = "skyblue", alpha = 0.6, trim = FALSE) +
  theme_minimal() +
  labs(title = "")

### Grafico de densidad para dojis

library(ggplot2)
library(dplyr)

# Filtrar los datos
datos.filt2.doji.gd <- datos.filt2.doji %>%
  filter(dragonfly == 1 | gravestone == 1)

# Crear el gráfico de CDF
ggplot(datos.filt2.doji.gd, aes(x = porc_diff_nd, color = factor(dragonfly + 2 * gravestone))) +
  stat_ecdf(geom = "step", size = 1) +
  scale_color_manual(values = c("blue", "red"), labels = c("Dragonfly", "Gravestone")) +
  labs(title = "Comparación de CDFs",
       x = "Porcentaje de Diferencia", 
       y = "Probabilidad Acumulada", 
       color = "Tipo de Doji") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Ampliamos la muestra

tickers <- list('JNJ','PG','KO','BP','CVX')
muestra <- datos.filt %>% 
  filter(Ticker %in% tickers)

muestra.doji <- muestra %>%
  mutate(
    dragonfly = ifelse(
      (abs(Close - Open) / ((Open + Close) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - High) / ((((Open + Close) / 2) + High) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - Low) / ((((Open + Close) / 2) + Low) / 2) > 0.01),
      1, 0
    ),
    gravestone = ifelse(
      (abs(Close - Open) / ((Open + Close) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - Low) / ((((Open + Close) / 2) + Low) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - High) / ((((Open + Close) / 2) + High) / 2) > 0.01),
      1, 0
    )
  )

sum(muestra.doji$dragonfly)
## [1] 2501
sum(muestra.doji$gravestone)
## [1] 2077

Proporciones poblacionales para la muestra ampliada

muestra.doji <- muestra.doji %>%
  mutate(increase_nd = ifelse(is.na(lead(Close)), FALSE, Close < lead(Close))) %>%
  arrange(Ticker)

p_pob_increase <- sum(muestra.doji$increase_nd)/nrow(muestra.doji)

muestra.doji.dragon <- muestra.doji %>%
  filter(dragonfly == 1)

p_dragon_increase <- sum(muestra.doji.dragon$increase_nd)/nrow(muestra.doji.dragon)

muestra.doji.grave <- muestra.doji %>%
  filter(gravestone == 1)

p_grave_decrease <- 1 - sum(muestra.doji.grave$increase_nd)/nrow(muestra.doji.grave)

p_pob_decrease <- 1 - p_pob_increase

p_pob_increase
## [1] 0.5015108
p_dragon_increase
## [1] 0.5417833
p_pob_decrease
## [1] 0.4984892
p_grave_decrease
## [1] 0.4597978

Diferencias porcentuales para la muestra ampliada

muestra.doji <- muestra.doji %>%
  mutate(porc_diff_nd = ifelse(is.na(lead(Close)), 0, (lead(Close) - Close)/Close))

p_mean_diff <- mean(muestra.doji$porc_diff_nd)

muestra.doji.dragon <- muestra.doji %>%
  filter(dragonfly == 1)

mean_dragon_diff <- mean(muestra.doji.dragon$porc_diff_nd)

muestra.doji.grave <- muestra.doji %>%
  filter(gravestone == 1)

mean_grave_diff <- mean(muestra.doji.grave$porc_diff_nd)
calcular_dif_porcentual <- function(datos) {
  datos <- datos %>%
    mutate(
      diff_1 = (lead(Close, 1) - Close) / Close,
      diff_2 = (lead(Close, 2) - Close) / Close,
      diff_3 = (lead(Close, 3) - Close) / Close,
      diff_4 = (lead(Close, 4) - Close) / Close,
      diff_5 = (lead(Close, 5) - Close) / Close,
      diff_6 = (lead(Close, 6) - Close) / Close,
      diff_7 = (lead(Close, 7) - Close) / Close
    )

  # Función auxiliar para calcular los promedios de diferencias
  calcular_promedios <- function(df) {
    colMeans(df %>% select(starts_with("diff_")), na.rm = TRUE)
  }

  # Calcular promedios para la población total, con Gravestone y con Dragonfly
  total_mean <- calcular_promedios(datos)
  grave_mean <- calcular_promedios(datos %>% filter(gravestone == 1))
  dragon_mean <- calcular_promedios(datos %>% filter(dragonfly == 1))

  # Crear la tabla final
  tabla_resultado <- rbind(
    "Total" = total_mean,
    "Gravestone Doji" = grave_mean,
    "Dragonfly Doji" = dragon_mean
  )

  return(as.data.frame(tabla_resultado))
}

# Uso de la función
tabla_acciones <- calcular_dif_porcentual(muestra.doji)
print(tabla_acciones)
##                       diff_1       diff_2      diff_3      diff_4      diff_5
## Total           5.403829e-04 0.0010864445 0.001625822 0.002163637 0.002695216
## Gravestone Doji 2.000267e-03 0.0032636286 0.004154459 0.005103181 0.005753727
## Dragonfly Doji  3.745869e-05 0.0002723454 0.001118714 0.001484956 0.002207417
##                      diff_6      diff_7
## Total           0.003227896 0.003757427
## Gravestone Doji 0.006845586 0.006844590
## Dragonfly Doji  0.002806054 0.003325673

Graficos para la muestra ampliada

muestra.doji.gd <- muestra.doji %>%
  filter(dragonfly == 1 | gravestone == 1)

ggplot(muestra.doji.gd, aes(x = factor(dragonfly)
                                , y = porc_diff_nd, fill = dragonfly)) +
  geom_violin(fill = "skyblue", alpha = 0.6, trim = FALSE) +
  theme_minimal() +
  labs(title = "")

muestra.doji.gd <- muestra.doji %>%
  filter(dragonfly == 1 | gravestone == 1)


ggplot(muestra.doji.gd, aes(x = porc_diff_nd, color = factor(dragonfly + 2 * gravestone))) +
  stat_ecdf(geom = "step", size = 1) +
  scale_color_manual(values = c("blue", "red"), labels = c("Dragonfly", "Gravestone")) +
  labs(title = "Comparación de CDFs",
       x = "Porcentaje de Diferencia", 
       y = "Probabilidad Acumulada", 
       color = "Tipo de Doji") +
  theme_minimal()

Test de Hipotesis

Para cada tipo de Doji, compararemos la proporción observada con la proporción general de la población utilizando una prueba de proporciones.

Hipótesis para Dragonfly Doji

H0: La probabilidad de aumento después de un Dragonfly Doji es igual a la probabilidad general de aumento (pdragon = ppob) Ha: La probabilidad de aumento después de un Dragonfly Doji es diferente de la probabilidad general (pdragon != ppob)

Hipótesis para Gravestone Doji H0: La probabilidad de disminución después de un Gravestone Doji es igual a la probabilidad general de disminución (pgrave = ppob) Ha: La probabilidad de disminución después de un Gravestone Doji es diferente de la probabilidad general (pgrave != ppob)

# Calcular proporciones generales
p_pob_increase <- sum(muestra.doji$increase_nd) / nrow(muestra.doji)
p_pob_decrease <- 1 - p_pob_increase

# Número de observaciones en cada subconjunto
n_dragon <- nrow(muestra.doji.dragon)
n_grave <- nrow(muestra.doji.grave)

# Casos donde el precio aumentó después de un Dragonfly Doji
x_dragon <- sum(muestra.doji.dragon$increase_nd)

# Casos donde el precio disminuyó después de un Gravestone Doji
x_grave <- sum(muestra.doji.grave$increase_nd)

# Prueba de proporciones para Dragonfly Doji
test_dragon <- prop.test(x_dragon, n_dragon, p = p_pob_increase, alternative = "two.sided")

# Prueba de proporciones para Gravestone Doji
test_grave <- prop.test(n_grave - x_grave, n_grave, p = p_pob_decrease, alternative = "two.sided")

# Mostrar resultados
test_dragon
## 
##  1-sample proportions test with continuity correction
## 
## data:  x_dragon out of n_dragon, null probability p_pob_increase
## X-squared = 16.065, df = 1, p-value = 6.122e-05
## alternative hypothesis: true p is not equal to 0.5015108
## 95 percent confidence interval:
##  0.5220066 0.5614305
## sample estimates:
##         p 
## 0.5417833
test_grave
## 
##  1-sample proportions test with continuity correction
## 
## data:  n_grave - x_grave out of n_grave, null probability p_pob_decrease
## X-squared = 12.283, df = 1, p-value = 0.0004571
## alternative hypothesis: true p is not equal to 0.4984892
## 95 percent confidence interval:
##  0.4382188 0.4815269
## sample estimates:
##         p 
## 0.4597978
datos.filt.doji <- datos.filt %>%
  arrange(Ticker) %>%
  mutate(
    dragonfly = ifelse(
      (abs(Close - Open) / ((Open + Close) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - High) / ((((Open + Close) / 2) + High) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - Low) / ((((Open + Close) / 2) + Low) / 2) > 0.01),
      1, 0
    ),
    gravestone = ifelse(
      (abs(Close - Open) / ((Open + Close) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - Low) / ((((Open + Close) / 2) + Low) / 2) < 0.005) &
      (abs(((Open + Close) / 2) - High) / ((((Open + Close) / 2) + High) / 2) > 0.01),
      1, 0
    ),
    increase_nd = ifelse(is.na(lead(Close)), FALSE, Close < lead(Close)),
    porc_diff_nd = ifelse(is.na(lead(Close)), 0, (lead(Close) - Close)/Close)
  )

datos.filt.dragon <- datos.filt.doji %>%
  filter(dragonfly == 1)

datos.filt.grave <- datos.filt.doji %>%
  filter(gravestone == 1)

# Calcular proporciones generales
p_pob_increase2 <- sum(datos.filt.doji$increase_nd) / nrow(datos.filt.doji)
p_pob_decrease2 <- 1 - p_pob_increase2

# Número de observaciones en cada subconjunto
n_dragon2 <- nrow(datos.filt.dragon)
n_grave2 <- nrow(datos.filt.grave)

# Casos donde el precio aumentó después de un Dragonfly Doji
x_dragon2 <- sum(datos.filt.dragon$increase_nd)

# Casos donde el precio disminuyó después de un Gravestone Doji
x_grave2 <- sum(datos.filt.grave$increase_nd)

# Prueba de proporciones para Dragonfly Doji
test_dragon2 <- prop.test(x_dragon2, n_dragon2, p = p_pob_increase2, alternative = "two.sided")

# Prueba de proporciones para Gravestone Doji
test_grave2 <- prop.test(n_grave2 - x_grave2, n_grave2, p = p_pob_decrease2, alternative = "two.sided")

# Mostrar resultados
test_dragon2
## 
##  1-sample proportions test with continuity correction
## 
## data:  x_dragon2 out of n_dragon2, null probability p_pob_increase2
## X-squared = 98.763, df = 1, p-value < 2.2e-16
## alternative hypothesis: true p is not equal to 0.4776535
## 95 percent confidence interval:
##  0.4300000 0.4456578
## sample estimates:
##         p 
## 0.4378135
test_grave2
## 
##  1-sample proportions test with continuity correction
## 
## data:  n_grave2 - x_grave2 out of n_grave2, null probability p_pob_decrease2
## X-squared = 41.863, df = 1, p-value = 9.788e-11
## alternative hypothesis: true p is not equal to 0.5223465
## 95 percent confidence interval:
##  0.4865403 0.5032393
## sample estimates:
##         p 
## 0.4948884
# Calcular la media poblacional general
pob_mean <- mean(datos.filt.doji$porc_diff_nd)

# Prueba t para Dragonfly Doji
test_dragon3 <- t.test(datos.filt.dragon$porc_diff_nd, 
                       mu = pob_mean, 
                       alternative = "two.sided")

# Prueba t para Gravestone Doji
test_grave3 <- t.test(datos.filt.grave$porc_diff_nd, 
                       mu = pob_mean, 
                       alternative = "two.sided")

# Mostrar resultados
test_dragon3
## 
##  One Sample t-test
## 
## data:  datos.filt.dragon$porc_diff_nd
## t = -8.3215, df = 15549, p-value < 2.2e-16
## alternative hypothesis: true mean is not equal to 0.0004795564
## 95 percent confidence interval:
##  -0.0012818762 -0.0006102639
## sample estimates:
##   mean of x 
## -0.00094607
test_grave3
## 
##  One Sample t-test
## 
## data:  datos.filt.grave$porc_diff_nd
## t = 6.1577, df = 13889, p-value = 7.583e-10
## alternative hypothesis: true mean is not equal to 0.0004795564
## 95 percent confidence interval:
##  0.001126624 0.001730944
## sample estimates:
##   mean of x 
## 0.001428784

Golden y Death Cross

# Load necessary library
library(xts)

# Example data (assuming df is your data frame)
datos.filt2$Date <- as.Date(datos.filt2$Date)  # Convert Date column to Date type

# Convert to xts object (excluding non-numeric columns)
df_xts <- xts(datos.filt2[, -c(1,2)], order.by = datos.filt2$Date)

# View the xts object
print(df_xts)
##                    Open         High          Low        Close   Volume
## 1962-01-02  0.004007111  0.004116209  0.004007111  0.004007111   806400
## 1962-01-03  0.003947602  0.003947602  0.003858325  0.003917833  1574400
## 1962-01-04  0.003927766  0.003977356  0.003927766  0.003947602   844800
## 1962-01-05  0.003947603  0.003997192  0.003848408  0.003858326  1420800
## 1962-01-08  0.003828574  0.003828574  0.003744263  0.003818656  2035200
## 1962-01-09  0.003818655  0.003907917  0.003788901  0.003888081   960000
## 1962-01-10  0.003888078  0.003962470  0.003848406  0.003907914  1612800
## 1962-01-11  0.003907915  0.003947602  0.003888079  0.003947602   614400
## 1962-01-12  0.003947602  0.003947602  0.003878161  0.003917833   883200
## 1962-01-15  0.003907919  0.003907919  0.003868248  0.003878166   614400
##        ...                                                             
## 2024-10-22 69.000000000 69.750000000 68.680000305 69.449996948 18603300
## 2024-10-23 66.989997864 68.699996948 66.580001831 68.010002136 24655200
## 2024-10-24 67.650001526 68.040000916 66.949996948 67.300003052 17568800
## 2024-10-25 67.069999695 67.699996948 66.790000916 66.919998169 11138100
## 2024-10-28 66.959999084 67.400001526 66.599998474 66.669998169 10761400
## 2024-10-29 66.290000916 66.339996338 65.519996643 65.559997559 16525900
## 2024-10-30 65.510002136 66.540000916 65.319999695 65.919998169 14177800
## 2024-10-31 65.809997559 65.989997864 65.260002136 65.309997559 13383700
## 2024-11-01 65.470001221 65.660003662 64.889999390 65.010002136 12158900
## 2024-11-04 65.470001221 65.419998169 65.019996643 65.199996948   896261
##            Dividends Stock.Splits
## 1962-01-02         0            0
## 1962-01-03         0            0
## 1962-01-04         0            0
## 1962-01-05         0            0
## 1962-01-08         0            0
## 1962-01-09         0            0
## 1962-01-10         0            0
## 1962-01-11         0            0
## 1962-01-12         0            0
## 1962-01-15         0            0
##        ...                       
## 2024-10-22         0            0
## 2024-10-23         0            0
## 2024-10-24         0            0
## 2024-10-25         0            0
## 2024-10-28         0            0
## 2024-10-29         0            0
## 2024-10-30         0            0
## 2024-10-31         0            0
## 2024-11-01         0            0
## 2024-11-04         0            0

Calculamos los EMAs

macd50 <- EMA(df_xts$Close, 50)

macd200 <- EMA(df_xts$Close, 200)

macd50
##                 EMA
## 1962-01-02       NA
## 1962-01-03       NA
## 1962-01-04       NA
## 1962-01-05       NA
## 1962-01-08       NA
## 1962-01-09       NA
## 1962-01-10       NA
## 1962-01-11       NA
## 1962-01-12       NA
## 1962-01-15       NA
##        ...         
## 2024-10-22 69.83285
## 2024-10-23 69.76137
## 2024-10-24 69.66484
## 2024-10-25 69.55720
## 2024-10-28 69.44398
## 2024-10-29 69.29167
## 2024-10-30 69.15944
## 2024-10-31 69.00849
## 2024-11-01 68.85168
## 2024-11-04 68.70848
macd200
##                 EMA
## 1962-01-02       NA
## 1962-01-03       NA
## 1962-01-04       NA
## 1962-01-05       NA
## 1962-01-08       NA
## 1962-01-09       NA
## 1962-01-10       NA
## 1962-01-11       NA
## 1962-01-12       NA
## 1962-01-15       NA
##        ...         
## 2024-10-22 65.43913
## 2024-10-23 65.46471
## 2024-10-24 65.48297
## 2024-10-25 65.49727
## 2024-10-28 65.50894
## 2024-10-29 65.50945
## 2024-10-30 65.51353
## 2024-10-31 65.51151
## 2024-11-01 65.50652
## 2024-11-04 65.50347
#resta de los dos

Identificamos las cruces

library(xts)

macddf <- data.frame(
  Date = index(macd50),
  ema50 = coredata(macd50),
  ema200 = coredata(macd200)
)

macddf <- macddf %>%
  mutate(ema50may = EMA - EMA.1 > 0,
         cross = ifelse(ema50may != lead(ema50may),ifelse(ema50may == TRUE, -1, 1) , 0)) %>%
  left_join(datos.filt2, by = "Date") 

macddf_cross <- macddf %>%
  filter(cross != 0)

golden <- count(macddf_cross, cross == 1)

Simulacion de estrategias

Simulamos una estrategia de trading basada en los resultados

macddf_valid <- macddf %>%
  filter(!is.na(Close), !is.na(cross))

initial_state <- list(
  cash = 10000,
  stocks = 0,
  saldo = 10000
)

# Acumulador paso a paso
sim_list <- accumulate(
  1:nrow(macddf_valid),
  .init = initial_state,
  .f = function(state, i) {
    row <- macddf_valid[i, ]
    
    new_state <- state

    if (!is.na(row$cross)) {
      if (row$cross == 1) {
        # Golden Cross → Comprar con todo el cash
        new_state$stocks <- state$cash / row$Close
        new_state$cash <- 0
      } else if (row$cross == -1) {
        # Death Cross → Vender todo
        new_state$cash <- state$stocks * row$Close
        new_state$stocks <- 0
      }
    }

    # Recalcular el saldo total
    new_state$saldo <- new_state$cash + new_state$stocks * row$Close
    return(new_state)
  }
)

# Convertir lista de estados a data frame
sim_df <- bind_rows(sim_list[-1])  # sacar .init
macddf_sim <- bind_cols(macddf_valid, sim_df)

Simulamos una estrategia de trading azarosa

n <- nrow(macddf_valid)
macddf_valid$azar <- 0

# Dividir en bloques
bloques <- floor(n / 75)

# Elegir una posición aleatoria dentro de cada bloque
idx_1 <- sapply(0:(75 - 1), function(i) {
  bloque_inicio <- i * bloques + 1
  bloque_fin <- min((i + 1) * bloques, n)
  sample(bloque_inicio:bloque_fin, 1)
})

# Asignar los 1
macddf_valid$azar[idx_1] <- 1
sim_list_aux <- accumulate(
  1:nrow(macddf_valid),
  .init = initial_state,
  .f = function(state, i) {
    row <- macddf_valid[i, ]
    
    new_state <- state

    if (!is.na(row$azar)) {
      if (row$azar == 1) {
        if(state$stocks == 0){
          new_state$stocks <- state$cash / row$Close
          new_state$cash <- 0
        }
        else if(state$stocks != 0){
          new_state$cash <- state$stocks * row$Close
          new_state$stocks <- 0
        }
      }
    }

    # Recalcular el saldo total
    new_state$saldo <- new_state$cash + new_state$stocks * row$Close
    return(new_state)
  }
)

# Convertir lista de estados a data frame
sim_df_aux <- bind_rows(sim_list_aux[-1])  # sacar .init
macddf_sim_azar <- bind_cols(macddf_valid, sim_df_aux)

Ahora lo repetimos 100 veces

library(purrr)
library(dplyr)

# Lista para guardar los resultados finales de saldo
resultados <- numeric(100)

# Loop de 100 repeticiones
for (k in 1:100) {
  # Paso 1: generar señales azarosas distribuidas
  n <- nrow(macddf_valid)
  macddf_valid$azar <- 0
  bloques <- floor(n / 75)
  idx_1 <- sapply(0:(75 - 1), function(i) {
    bloque_inicio <- i * bloques + 1
    bloque_fin <- min((i + 1) * bloques, n)
    sample(bloque_inicio:bloque_fin, 1)
  })
  macddf_valid$azar[idx_1] <- 1
  
  # Paso 2: correr simulación con accumulate
  initial_state <- list(stocks = 0, cash = 10000, saldo = 10000)
  
  sim_list_aux <- accumulate(
    1:nrow(macddf_valid),
    .init = initial_state,
    .f = function(state, i) {
      row <- macddf_valid[i, ]
      new_state <- state
      
      if (!is.na(row$azar) && row$azar == 1) {
        if (state$stocks == 0) {
          new_state$stocks <- state$cash / row$Close
          new_state$cash <- 0
        } else {
          new_state$cash <- state$cash + state$stocks * row$Close
          new_state$stocks <- 0
        }
      }
      
      new_state$saldo <- new_state$cash + new_state$stocks * row$Close
      return(new_state)
    }
  )
  
  sim_df_aux <- bind_rows(sim_list_aux[-1])
  
  # Paso 3: guardar el último saldo
  resultados[k] <- sim_df_aux$saldo[nrow(sim_df_aux)]
}

# Ver resultados
resultados
##   [1]  3698740.3   835784.4   338351.4   281748.0  1400804.4  2980695.7
##   [7]   542468.6   745426.7  1522526.0  2092085.7   298781.6  3424121.9
##  [13]  8547231.9  1878382.5  1818100.6  2075669.3  3232076.8  1323201.2
##  [19]  2599822.1   918016.4  1929264.4  1586320.8   950142.1   906465.2
##  [25]   798990.3   768410.3   731628.4  1669230.2  1701375.0   329882.0
##  [31]   623774.9   973609.1  1739023.1   997099.2 12229792.4  1248666.4
##  [37]   410892.4  1040716.2  1317649.3  9007046.5  3239114.9  7254870.2
##  [43]  5349645.1  4442919.5  1201810.1   195031.9  4857429.6  1678016.9
##  [49]  1735544.1  1972077.9  5522814.1  4792454.6  1446723.3   608740.6
##  [55]  4675408.1  5530319.1   328451.2   945360.1  2020428.1  3232374.1
##  [61]  2718535.7  1139726.7   754379.0   940077.4  1060529.8   396075.8
##  [67]  1332668.5  1162449.1  4255995.3   367556.3  2345536.7   964452.1
##  [73]   908661.5   996161.8   374166.7  1633482.9  1865038.9   427100.0
##  [79]   727428.2  1161960.2  3023415.6  1690221.3  1543601.7   656905.1
##  [85]   578198.6   540118.6  1456845.0   356191.9  1052260.2   956568.7
##  [91]  2385072.1  2655446.8  1983978.4   930596.9   445139.5   228099.8
##  [97]   426676.1  1955112.8   626368.4  3513656.9
summary(resultados)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##   195032   752141  1320425  1950800  2155448 12229792

Grafico de la distribucion de los resultados de la estrategia azarosa

hist(resultados,
     breaks = 100,  
     col = "skyblue",
     border = "white",
     main = "Histograma del saldo final en 30 simulaciones aleatorias",
     xlab = "Saldo final",
     ylab = "Frecuencia")

# Línea del promedio
abline(v = mean(resultados), col = "red", lwd = 2, lty = 2)
legend("topright", legend = paste("Media =", round(mean(resultados), 2)),
       col = "red", lwd = 2, lty = 2, bty = "n")

Test de Hipotesis para las estrategias

saldo_estrategia <- macddf_sim$saldo[nrow(macddf_sim)]
t.test(resultados, mu = saldo_estrategia, alternative = "less")
## 
##  One Sample t-test
## 
## data:  resultados
## t = -169.85, df = 99, p-value < 2.2e-16
## alternative hypothesis: true mean is less than 36013831
## 95 percent confidence interval:
##     -Inf 2283781
## sample estimates:
## mean of x 
##   1950800

Aplicandolo a todos los tickers

library(dplyr)
library(TTR)  # o library(quantmod), ya que EMA viene de ahí también
library(purrr)

# Asegurarse de que los datos estén ordenados por Ticker y Date
datos.filt <- datos.filt %>%
  arrange(Ticker, Date)

# Función para calcular EMA(50) y EMA(200)
calcular_emas <- function(df) {
  df$EMA_50 <- EMA(df$Close, n = 50)
  df$EMA_200 <- EMA(df$Close, n = 200)
  return(df)
}

# Aplicar la función a cada grupo (ticker)
datos.ema <- datos.filt %>%
  group_by(Ticker) %>%
  group_modify(~calcular_emas(.x)) %>%
  ungroup()

datos.ema
emadf <- datos.ema %>%
  select(Date, Ticker, Close, EMA_50, EMA_200) %>%
  filter(!is.na(EMA_50), !is.na(EMA_200)) %>%
  arrange(Ticker, Date) %>%
  group_by(Ticker) %>%
  mutate(
    ema50may = EMA_50 > EMA_200,
    cross = ifelse(ema50may != lag(ema50may),
                   ifelse(ema50may == TRUE, 1, -1),
                   0),
    # Agregar NA si el ticker cambia respecto al anterior
    cross = ifelse(Ticker != lag(Ticker), NA, cross)
  ) %>%
  ungroup()

emadf
simular_estrategia <- function(df) {
  df <- df %>%
    filter(!is.na(Close), !is.na(cross)) %>%
    arrange(Date)
  
  if (nrow(df) == 0) return(NULL)  # evitar errores

  initial_state <- list(cash = 10000, stocks = 0, saldo = 10000)
  
  sim_list <- purrr::accumulate(
    1:nrow(df),
    .init = initial_state,
    .f = function(state, i) {
      row <- df[i, ]
      new_state <- state
      
      if (!is.na(row$cross)) {
        if (row$cross == 1) {
          # Golden Cross: Comprar
          new_state$stocks <- state$cash / row$Close
          new_state$cash <- 0
        } else if (row$cross == -1) {
          # Death Cross: Vender
          new_state$cash <- state$cash + state$stocks * row$Close
          new_state$stocks <- 0
        }
      }
      
      new_state$saldo <- new_state$cash + new_state$stocks * row$Close
      return(new_state)
    }
  )

  sim_df <- bind_rows(sim_list[-1])  # quitar estado inicial
  df_result <- bind_cols(df, sim_df)
  return(df_result)
}
# Dividir el dataframe por ticker
tickers_split <- emadf %>%
  group_by(Ticker) %>%
  group_split()

# Aplicar la simulación a cada ticker
resultados_por_ticker <- lapply(tickers_split, simular_estrategia)

# Unir todos los resultados en un único dataframe
resultados_finales <- bind_rows(resultados_por_ticker)
saldo_final_por_ticker <- resultados_finales %>%
  group_by(Ticker) %>%
  filter(!is.na(saldo)) %>%
  slice_tail(n = 1) %>%
  select(Ticker, Date, saldo)

print(saldo_final_por_ticker)
## # A tibble: 29 × 3
## # Groups:   Ticker [29]
##    Ticker Date           saldo
##    <chr>  <date>         <dbl>
##  1 AA     2024-11-04    39778.
##  2 AEP    2024-11-04   350985.
##  3 BA     2024-11-04 15523962.
##  4 BP     2024-11-04   257503.
##  5 CAT    2024-11-04  1220865.
##  6 CNP    2024-11-04   160527.
##  7 CVX    2024-11-04  6380868.
##  8 DIS    2024-11-04  2182131.
##  9 DTE    2024-11-04  1083483.
## 10 ED     2024-11-04   885418.
## # ℹ 19 more rows
simular_estrategia_azar <- function(df, n_senales = 75) {
  df <- df %>%
    filter(!is.na(Close)) %>%
    arrange(Date)
  
  n <- nrow(df)
  if (n < n_senales) return(NULL)

  # Inicializar columna de azar
  df$azar <- 0

  # Crear señales aleatorias distribuidas
  bloques <- floor(n / n_senales)
  idx_1 <- sapply(0:(n_senales - 1), function(i) {
    bloque_inicio <- i * bloques + 1
    bloque_fin <- min((i + 1) * bloques, n)
    sample(bloque_inicio:bloque_fin, 1)
  })
  df$azar[idx_1] <- 1
  
  df_ultimas_filas <- df %>%
    slice_tail(n = 1) %>%
    ungroup()
  
  df <- df %>%
    filter(azar == 1)
  
      
  df <- bind_rows(df, df_ultimas_filas) %>%
    distinct(Date, .keep_all = TRUE) %>%
    arrange(Date)
  
  n <- nrow(df)
  
  # Simulación
  initial_state <- list(cash = 10000, stocks = 0, saldo = 10000)
  
  sim_list <- purrr::accumulate(
    1:n,
    .init = initial_state,
    .f = function(state, i) {
      row <- df[i, ]
      new_state <- state
      
      if (state$stocks == 0) {
        new_state$stocks <- state$cash / row$Close
        new_state$cash <- 0
      } else {
        new_state$cash <- state$cash + state$stocks * row$Close
        new_state$stocks <- 0
      }
      
      new_state$saldo <- new_state$cash + new_state$stocks * row$Close
      return(new_state)
    }
  )

  sim_df <- bind_rows(sim_list[-1])
  df_result <- bind_cols(df, sim_df)
  return(df_result)
}
library(progress)

n_repeticiones <- 100
lista_saldos_finales <- list()

# Crear barra de progreso
pb <- progress_bar$new(
  format = "[:bar] :percent | Iteración :current/:total | Tiempo restante: :eta",
  total = n_repeticiones,
  clear = FALSE,
  width = 60
)

for (i in 1:n_repeticiones) {
  pb$tick()  # Actualizar barra
  
  # Simular estrategia para todos los tickers
  resultados_iteracion <- lapply(tickers_split, simular_estrategia_azar) %>% 
    bind_rows()
  
  # Extraer saldos finales
  lista_saldos_finales[[i]] <- resultados_iteracion %>%
    group_by(Ticker) %>%
    filter(!is.na(saldo)) %>%
    slice_tail(n = 1) %>%
    select(Ticker, Date, saldo)
  
}

resultados_saldos_finales_azar <- bind_rows(lista_saldos_finales)
resultados_saldos_finales_azar

Test de Hipotesis Estrategias

# Paso 1: Calcular el promedio de cada ticker en las simulaciones
resultado_promedios <- resultados_saldos_finales_azar %>%
  group_by(Ticker) %>%
  summarise(saldo_azar = mean(saldo))

# Paso 2: Unir con el saldo de la estrategia
comparacion <- saldo_final_por_ticker %>%
  select(Ticker, saldo_estrategia = saldo) %>%
  inner_join(resultado_promedios, by = "Ticker")

# Paso 3: Test t pareado (una cola)
t.test(comparacion$saldo_estrategia, comparacion$saldo_azar,
       paired = TRUE,
       alternative = "greater")
## 
##  Paired t-test
## 
## data:  comparacion$saldo_estrategia and comparacion$saldo_azar
## t = 2.5331, df = 28, p-value = 0.008595
## alternative hypothesis: true mean difference is greater than 0
## 95 percent confidence interval:
##  931980.6      Inf
## sample estimates:
## mean difference 
##         2837681

Optimizacion de EMAs

calcular_saldo_final_ema <- function(df=datos.filt, ema1, ema2) {
  
  calcular_emas <- function(df, ema1, ema2) {
    df %>%
      mutate(
        EMA1 = EMA(Close, n = ema1),
        EMA2 = EMA(Close, n = ema2)
      )
  }
  
  # Uso:
  df <- df %>%
    group_by(Ticker) %>%
    calcular_emas(ema1, ema2) %>%
    ungroup()
  
  df <- df %>%
    select(Date, Ticker, Close, EMA1, EMA2) %>%
    filter(!is.na(EMA1), !is.na(EMA2)) %>%
    arrange(Ticker, Date) %>%
    group_by(Ticker) %>%
    mutate(
      ema1may = EMA1 > EMA2,
      cross = ifelse(ema1may != lag(ema1may),
                     ifelse(ema1may == TRUE, 1, -1),
                     0),
      # Agregar NA si el ticker cambia respecto al anterior
      cross = ifelse(Ticker != lag(Ticker), NA, cross)
    ) %>%
    ungroup()
  
  df_ultimas_filas <- df %>%
    group_by(Ticker) %>%
    slice_tail(n = 1) %>%
    ungroup()
  
  df <- df %>%
    filter(cross != 0)
    
  df <- bind_rows(df, df_ultimas_filas) %>%
    distinct(Ticker, Date, .keep_all = TRUE) %>%
    arrange(Ticker, Date)
  
  df_split <- df %>%
    group_by(Ticker) %>%
    group_split()
  
  df_result <- lapply(df_split, simular_estrategia) %>%
    bind_rows() %>%
    group_by(Ticker) %>%
    filter(!is.na(saldo)) %>%
    slice_tail(n = 1)
  
  return(mean(df_result$saldo))
  
}
library(progress)
library(reshape2)
## 
## Adjuntando el paquete: 'reshape2'
## The following object is masked from 'package:tidyr':
## 
##     smiths
saldo_base <- mean(saldo_final_por_ticker$saldo)

ema1 <- seq(10, 250, by = 10)    
ema2 <- seq(150, 400, by = 10)

# Crear matriz vacía
matriz_resultados <- matrix(
  nrow = length(ema1),
  ncol = length(ema2),
  dimnames = list(paste0("EMA1_", ema1), paste0("EMA2_", ema2))
)

pb <- progress_bar$new(
  format = "[:bar] :percent | Fila: :current/:total | Tiempo: :elapsedfull",
  total = length(ema1) * length(ema2),  # Total de iteraciones
  clear = FALSE,
  width = 60
)

# Llenar la matriz con mapply
for (i in seq_along(ema1)) {
  for (j in seq_along(ema2)) {
    if (ema1[i] >= ema2[j]) {
      matriz_resultados[i, j] <- NA
    } else {
      matriz_resultados[i, j] <- calcular_saldo_final_ema(datos.filt, ema1[i], ema2[j])/saldo_base
    }
    
    
    pb$tick()
  }
}
library(plotly)
## 
## Adjuntando el paquete: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
plot_ly(
  z = ~matriz_resultados,
  x = colnames(matriz_resultados),
  y = rownames(matriz_resultados),
  colorscale = list(
    c(0, "blue"),    # Mínimo: azul
    c(0.5, "white"), # Medio: blanco (ajusta el 0.5 según tus datos)
    c(1, "red")      # Máximo: rojo
  ),
  type = "heatmap"
) %>%
  layout(title = "Promedio en base a EMAs")

Matriz de cantidad de operaciones

calcular_operaciones_ema <- function(df=datos.filt, ema1, ema2) {
  
  calcular_emas <- function(df, ema1, ema2) {
    df %>%
      mutate(
        EMA1 = EMA(Close, n = ema1),
        EMA2 = EMA(Close, n = ema2)
      )
  }
  
  # Uso:
  df <- df %>%
    group_by(Ticker) %>%
    calcular_emas(ema1, ema2) %>%
    ungroup()
  
  df <- df %>%
    select(Date, Ticker, Close, EMA1, EMA2) %>%
    filter(!is.na(EMA1), !is.na(EMA2)) %>%
    arrange(Ticker, Date) %>%
    group_by(Ticker) %>%
    mutate(
      ema1may = EMA1 > EMA2,
      cross = ifelse(ema1may != lag(ema1may),
                     ifelse(ema1may == TRUE, 1, -1),
                     0),
      # Agregar NA si el ticker cambia respecto al anterior
      cross = ifelse(Ticker != lag(Ticker), NA, cross)
    ) %>%
    ungroup()
  
  df_ultimas_filas <- df %>%
    group_by(Ticker) %>%
    slice_tail(n = 1) %>%
    ungroup()
  
  df <- df %>%
    filter(cross != 0)
  
  return(nrow(df)/29)
}
ema1 <- seq(40, 250, by = 10)    
ema2 <- seq(150, 400, by = 10)

# Crear matriz vacía
matriz_resultados2 <- matrix(
  nrow = length(ema1),
  ncol = length(ema2),
  dimnames = list(paste0("EMA1_", ema1), paste0("EMA2_", ema2))
)

pb <- progress_bar$new(
  format = "[:bar] :percent | Fila: :current/:total | Tiempo: :elapsedfull",
  total = length(ema1) * length(ema2),  # Total de iteraciones
  clear = FALSE,
  width = 60
)

# Llenar la matriz con mapply
for (i in seq_along(ema1)) {
  for (j in seq_along(ema2)) {
    if (ema1[i] >= ema2[j]) {
      matriz_resultados2[i, j] <- NA
    } else {
      matriz_resultados2[i, j] <- calcular_operaciones_ema(datos.filt, ema1[i], ema2[j])
    }
    
    pb$tick()
  }
}
plot_ly(
  z = ~matriz_resultados2,
  x = colnames(matriz_resultados2),
  y = rownames(matriz_resultados2),
  colorscale = list(
    c(0, "blue"),    # Mínimo: azul
    c(0.5, "white"), # Medio: blanco (ajusta el 0.5 según tus datos)
    c(1, "red")      # Máximo: rojo
  ),
  type = "heatmap"
) %>%
  layout(title = "Operaciones promedio en base a EMAs")